#include "KzDevice.h"
#include <QTime>
#include "QLogTool.h"

using namespace std;
using namespace KzDeviceConst;

ERRCODE KzDevice::Initialize(const string& devType)
{
    m_devType = devType;
    m_cio = make_unique<cioMst>(devType);
    if (m_cio == nullptr) {
        LOG("KzDevice[%s] Initialize Failed.", devType.c_str());
        return ERR_FAILURE;
    }
    ConfigCmdHandle();
    LOG("KzDevice Initialize as [" + devType + "]");

    return ERR_SUCCESS;
}

void KzDevice::run()
{
    m_port = make_unique<QSerialPort>(nullptr);
    if (m_port == nullptr) {
        LOG("KzDevice[%s] Initialize Failed.", m_devType.c_str());
        return;
    }
    LOG("KzDevice Start Running.");
    while (!m_isForceStop) {
        /* 接收端口数据 */
        RxProc(m_rxEvent);
        /* 逐一处理来自设备和界面的接收事件 */
        EventProc(m_rxEvent);
        /* 发送端口数据 */
        TxProc();
        /* 刷新设备状态 */
        UpdateDevState();
        /* 任务休眠 */
        msleep(KZDEVICE_MST_PERIOD);
    }
    LOG("KzDevice Stop.");
}

void KzDevice::EventProc(std::queue<MstCioObj>& rxEvnet)
{
    lock_guard<mutex> lk(m_rxEventMtx);
    while (!rxEvnet.empty()) {
        const auto& task = rxEvnet.front(); // 零拷贝实现
        auto itr = m_exeTable.find(task.type);
        if (itr != m_exeTable.end()) {
            itr->second(task.data.data(), task.data.size());
        }
        UpdateRecvTimeout();
        rxEvnet.pop();
    }
}

void KzDevice::RxProc(std::queue<MstCioObj>& rxEvnet)
{
    /* 硬件层：接收信息 */
    m_cio->SetBytesAvailable(static_cast<size_t>(m_port->bytesAvailable()));
    byteStream newRxData = RecvPortData(); // 拷贝一次
    if (newRxData.empty()) {
        return;
    }
    /* 协议层：接收缓存转换为事件 */
    lock_guard<mutex> lk(m_rxEventMtx);
    m_cio->RxData(newRxData, rxEvnet);
}

void KzDevice::TxProc()
{
    lock_guard<mutex> lk(m_txBufferMtx);
    if (m_txBuffer.empty()) {
        return;
    }
    const char* pData = reinterpret_cast<char*>(m_txBuffer.data());
    qint64 dataLen = static_cast<qint64>(m_txBuffer.size());
    m_port->write(pData, dataLen);
    m_port->waitForBytesWritten();
    m_txBuffer.clear();
}

void KzDevice::SlotPortLink(QString portName, LINK_CMD_TYPE cmd)
{
    MstCioObj task;
    if (cmd == LINK_TOGGLE) {
        task.type = (m_port->isOpen()) ? MST_DISCONNECT : MST_CONNECT;
    } else {
        task.type = (cmd == LINK_OPEN ? MST_CONNECT : MST_DISCONNECT);
    }

    string sPortName = portName.toStdString();
    task.data.assign(sPortName.begin(), sPortName.end());
    AddTask(task);
}

ERRCODE KzDevice::PingDevice()
{
    return ERR_SUCCESS;
}

int KzDevice::Pong(const uint8_t* data, size_t len)
{
    if (data == nullptr || len != sizeof (FrmPong)) {
        return ERR_FRMEERR;
    }
    const FrmPong* pkg = reinterpret_cast<const FrmPong*>(data);
    m_devID[0] = pkg->UUID[0];
    m_devID[1] = pkg->UUID[1];
    m_devID[2] = pkg->UUID[2];

    LOG("Receive PongMsg From: devID[0x%08x-0x%08x-0x%08x]", m_devID[0], m_devID[1], m_devID[2]);
    return ERR_SUCCESS;
}

void KzDevice::SlotPortError(QSerialPort::SerialPortError info)
{
    LOG("SlotPortError[%u]", info);
}

int KzDevice::MstDisconnect(const uint8_t *data, size_t len)
{
    if (m_port == nullptr || data == nullptr || len == 0) {
        return ERR_ASSERT;
    }
    if (!m_port->isOpen()) {
        LOG(m_port->portName() + " is not opened.");
        return ERR_SUCCESS;
    }
    string cmdPort(data, data + len);
    string currPort = m_port->portName().toStdString();
    if (cmdPort != currPort) {
        LOG( "Device is Linked via " + currPort + ", not " + cmdPort);
        return ERR_BADPARA;
    }
    m_port->close();
    m_cio->GetStatic()->isPortReady = false;
    LOG("KzDevice(via %s) Closed.", currPort.data());
    return ERR_SUCCESS;
}

int KzDevice::MstConnect(const uint8_t* data, size_t len)
{
    if (m_port == nullptr) {
        return ERR_ASSERT;
    }
    if(m_port->isOpen()) {
        LOG(m_port->portName() + " is Already Opened.");
        return ERR_SUCCESS;
    }
    string portName(data, data + len);
    m_port->setBaudRate(QSerialPort::Baud115200);
    m_port->setStopBits(QSerialPort::OneStop);
    m_port->setDataBits(QSerialPort::Data8);
    m_port->setParity(QSerialPort::NoParity);
    m_port->setPortName(QString::fromStdString(portName));
    m_port->setReadBufferSize(1024 * 10);

    /* 重新打开 */
    if (m_port->open(QIODevice::ReadWrite)) {
        m_port->readAll(); // 清缓存
        m_cio->GetStatic()->portName = portName;
        m_cio->GetStatic()->isPortReady = true;
        LOG("KzDevice Open Succeed via " + portName);
        return ERR_SUCCESS;
    } else {
        m_cio->GetStatic()->isPortReady = false;
        LOG("KzDevice Open Failed via " + portName);
        return ERR_FAILURE;
    }
}

byteStream KzDevice::RecvPortData()
{
    byteStream ret;
    if((m_port == nullptr) || (!m_port->isOpen())){
        return ret;
    }
    while (m_port->waitForReadyRead(1)) {
        qint64 byteCnt = m_port->bytesAvailable();
        QByteArray qRet = m_port->read(byteCnt);
        ret.insert(ret.end(), qRet.begin(), qRet.end());
    }

    return ret;
}

void KzDevice::SlotRequestDump()
{
    AddTask(MST_READDUMP);
}

int KzDevice::MstRequestDump(const uint8_t *data, size_t len)
{
    DISCARD(data);
    DISCARD(len);
    LOG("Requesting Device Dump...");
    TxPkg(MST_READDUMP);
    return ERR_SUCCESS;
}

int KzDevice::MstEchoDump(const uint8_t *data, size_t len)
{
    DISCARD(data);
    DISCARD(len);
    LOG("Undefined Dump Info");
    return ERR_SUCCESS;
}

bool KzDevice::IsParaUpdated()
{
    bool temp = m_isUpdated;
    m_isUpdated = false;
    return temp;
}

void KzDevice::TxPkg(pkgType type, const void* data, size_t len)
{
    lock_guard<mutex> lk(m_txBufferMtx);
    m_cio->TxPkg(m_txBuffer, type, data, len);
}

void KzDevice::AddTask(pkgType type)
{
    AddTask(MstCioObj(type));
}

void KzDevice::AddTask(pkgType type, const void* data, size_t len)
{
    AddTask(MstCioObj(type, data, len));
}

void KzDevice::AddTask(const MstCioObj& newTask)
{
    lock_guard<mutex> lk(m_rxEventMtx);
    m_rxEvent.push(newTask);
}

void KzDevice::UpdateRecvTimeout()
{
    m_timeout = QDateTime::currentDateTime().toTime_t();
}

void KzDevice::UpdateDevState()
{
    int currTimeStamper = QDateTime::currentDateTime().toTime_t();
    if ((currTimeStamper - m_timeout) > DEV_OFFLINE_TIMEOUT) {
        m_devState = COM_FAIL;
    } else {
        m_devState = DEV_RDY;
    }
}
